#include "Logger.h"
#include "Logformat.h"
#include "thread/current_thread.h"
#include "utility/assertion.h"
#include "utility/chrono.h"
#include "utility/feature.h"
#include "wrap/error.h"
#include "wrap/stdios.h"
#include "wrap/timing.h"

#include <cerrno>
#include <string>
#include <vector>


namespace _ {

static Logger::EnumLogLevel level = Logger::WARN;

static void default_flush_func() {
    Wrap::fflush_(stdout);
}

static void default_output_func(const char *ptr, std::size_t length) {
    ::fwrite(ptr, 1, length, stdout);
}

NO_DESTROY static Logger::FlushFunc flush_func = default_flush_func;
NO_DESTROY static Logger::OutputFunc output_func = default_output_func;

static constexpr std::size_t LEVEL_NAME_LENGTH = 7;
NO_DESTROY static std::vector<std::string> levelName = {
    "    RAW", "  FATAL", "  ERROR", "   WARN",
    "   INFO", "  DEBUG", "VERBOSE"};

}


Logger::EnumLogLevel Logger::LEVEL() {
    return _::level;
}

void Logger::RESET_FLUSH_FUNC() {
    _::flush_func = _::default_flush_func;
}

void Logger::RESET_OUTPUT_FUNC() {
    _::output_func = _::default_output_func;
}

void Logger::SET_LEVEL(EnumLogLevel l) {
    if (RAW <= l && l <= VERBOSE) {
        _::level = l;
    }
    ::printf("log level: %d\n", _::level);
}

void Logger::SET_FLUSH_FUNC(const FlushFunc &func) {
    assert(func);
    _::flush_func = func;
}

void Logger::SET_OUTPUT_FUNC(const OutputFunc &func) {
    assert(func);
    _::output_func = func;
}

Logger::Logger(const std::string_view &file, int line, EnumLogLevel level, bool err):
    _stream{},
    _file{file},
    _line{line},
    _level{level} {
    _log_time();
    _stream << '|';
    MAYBE_UNUSED thread_local auto tid = CurrentThread::tid();
    _stream.append(
        CurrentThread::tid_string(),
        static_cast<std::size_t>(CurrentThread::tid_string_length()));
    _stream << '|';
    _stream.append(_::levelName.at(level).c_str(), _::LEVEL_NAME_LENGTH);
    _stream << '|';
    if (err) {
        _stream << Wrap::strerror_r_(errno) << " (errono=" << errno << ")";
    }
}

Logger::~Logger() {
    if (_level > RAW) {
        _stream << " -> " << _file << ':' << _line << '\n';
    }
    _::output_func(
        stream().buffer().data(), static_cast<std::size_t>(stream().buffer().length()));
    if (_level == FATAL) {
        _::flush_func();
        ::abort();
    }
}


void Logger::_log_time() {
    static constexpr auto S_FORMAT = "%04d/%02d/%02d %02d:%02d:%02d";
    static constexpr auto S_FORMAT_LENGTH = 19;
    static constexpr auto US_FORMAT = ".%09lld";
    static constexpr auto US_FORMAT_LENGTH = 10;
    const auto count = Utility::epoch_count(Utility::now());
    const auto s = static_cast<Wrap::time_t_>(count / Utility::COUNT_PER_SEC);
    thread_local decltype(+s) sLast = 0;
    thread_local std::array<char, 1 << 6> tBuffer{};
    if (s != sLast) {
        sLast = s;
        struct tm t{};
        Wrap::localtime_r_(&s, &t);
        constexpr auto START_YEAR = 1900;
        ::snprintf(
            tBuffer.data(), tBuffer.size(), S_FORMAT, t.tm_year + START_YEAR, 
            t.tm_mon + 1, t.tm_mday, t.tm_hour, t.tm_min, t.tm_sec);
    }
    _stream.append(tBuffer.data(), S_FORMAT_LENGTH);
    _stream.append(
        Logformat{US_FORMAT, count % Utility::COUNT_PER_SEC}.data(),
        US_FORMAT_LENGTH);
}
